package Chess;

import java.util.Map;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.awt.Color;
import Chess.Pieces.*;
import Chess.Pieces.Piece.PieceType;

/**
 *	A class representing a Chess Board in a Chess Game.
 *
 *	@author		Jeff Chan, Ricky Saini
 */
public class Board {
	private HashMap<Square, Piece> grid;
	
	public Board() {
		grid = new HashMap<Square, Piece> ();
	}
	
	/**
	 *	Returns a HashMap of the Board grid
	 *
	 *	@return		the HashMap of the Board
	 */
	public HashMap<Square, Piece> getGrid() {
		return grid;
	}
	
	/**
	 *	Returns the Piece at the specified Square	
	 *
	 *	@param	sq	the specified Square index
	 *	@return		the Piece
	 */
	public Piece getPiece(Square sq) {
		return grid.get(sq);
	}
	
	/**
	 *	Returns true if Piece exists at the specified Square.
	 *	False if not.
	 *
	 *	@param	sq	the specified Square to check
	 *	@return		true if this Square has a Piece
	 */
	public boolean hasPiece(Square sq) {
		return getPiece(sq) != null;
	}
	
	/**
	 *	Inserts a Piece at the specified index key.
	 *
	 *	@param	key		the index Square
	 *	@param	value	the value Piece
	 */
	public void putPiece(Square key, Piece value) {
		grid.put(key, value);
	}
	
	/**
	 *	Moves one Piece from an existing Square to another.
	 *	
	 *	@param	current		the existing Square
	 *	@param	target		the destination Square
	 */
	public void movePiece(Piece piece, Square current, Square target) {
		if (piece != null && piece.equals(getPiece(current))) {
			grid.remove(current);
			grid.put(target, piece);
		}
	}
	
	/**
	 *	Removes the Piece at the specified Square.
	 *
	 *	@param	sq	the Square to remove Piece from
	 *	@return		the removed Piece
	 */
	public Piece removePiece(Square sq) {
		if(grid.containsKey(sq) && grid.get(sq) != null)
			return grid.remove(sq);
		else
			return null;
	}
	
	/**
	 *	Checks if a generic Square is in check.
	 *	Sets to true if is in check. False otherwise.
	 *
	 *	@param	checkSquare	the Square of the King
	 *	@param	color		the Color of the King
	 *	@return 			true if in check, false if not
	 */
	public boolean squareInCheck(Square checkSquare, Color color) {
		boolean inCheck = false;

		Set<Square> squares = grid.keySet();
		for (Iterator<Square> squareIter = squares.iterator(); squareIter.hasNext() && !inCheck; ) {
			Square square = squareIter.next();
			if (!getPiece(square).getColor().equals(color)) {
				Piece attackPiece = getPiece(square);
				
				if (attackPiece.canAttack(this, square, checkSquare))
					inCheck = true;
			}
		}

		return inCheck;
	}
	
	/**
	 *	Locations the Square of the King of a given Color.
	 *
	 *	@param	color		the Color of the King
	 *	@return 			the Square of the King
	 */
	private Square findKingSquare(Color color) {
		Square kingSquare = null;
		
		Set<Square> squares = grid.keySet();
		for (Iterator<Square> squareIter = squares.iterator(); squareIter.hasNext() && kingSquare == null; ) {
			Square square = squareIter.next();
			Piece piece = getPiece(square);
			
			if (piece.getPieceType().equals(PieceType.KING) && piece.getColor().equals(color))
				kingSquare = square;
		}
		
		return kingSquare;
	}
	
	/**
	 *	Checks if the King of a Color is in check.
	 *	Sets to true if is in check. False otherwise.
	 *
	 *	@param	checkSquare	the Square of the King
	 *	@param	color		the Color of the King
	 *	@return 			true if in check, false if not
	 */
	public boolean isInCheck(Color color) {
		return squareInCheck(findKingSquare(color), color);
	}
	
	/**
	 *	Checks if there is a clear path between two Squares.
	 *	
	 *	@param	startSquare 	the first Square
	 *	@param	endSquare		the second Square
	 *	@return					true if clear, false if not
	 */
	public boolean clearPathBetween(Square startSquare, Square endSquare) {
		boolean clearPathExists = false;
		
		if(startSquare.equals(endSquare))
			clearPathExists = false;
		else if(startSquare.isInSameFile(endSquare))
			clearPathExists = clearPathInFile(startSquare, endSquare);
		else if(startSquare.isInSameRank(endSquare))
			clearPathExists = clearPathInRank(startSquare, endSquare);
		else if(startSquare.isDiagonal(endSquare))
			clearPathExists = clearPathInDiagonal(startSquare, endSquare);
		
		return clearPathExists;
	}
	
	/**
	 *	Checks if there is a clear rank path between two Squares.
	 *	
	 *	@param	startSquare 	the first Square
	 *	@param	endSquare		the second Square
	 *	@return					true if clear, false if not
	 */
	private boolean clearPathInRank(Square startSquare, Square endSquare) {
		boolean clearPathExists = true;
		
		Set<Square> squaresBetween = new HashSet<Square>();
		for(char file = 'a'; file <= 'h'; file++) {
			if(fileBetween(file, startSquare.getFile(), endSquare.getFile()))
				squaresBetween.add(new Square(file, startSquare.getRank()));
		}
		
		for(Square square : squaresBetween) {
			if(hasPiece(square))
				clearPathExists = false;
		}
		
		return clearPathExists;
	}
	
	/**
	 *	Checks if there is a clear file path between two Squares.
	 *	
	 *	@param	startSquare 	the first Square
	 *	@param	endSquare		the second Square
	 *	@return					true if clear, false if not
	 */
	private boolean clearPathInFile(Square startSquare, Square endSquare) {
		boolean clearPathExists = true;
		
		Set<Square> squaresBetween = new HashSet<Square>();
		for(char rank = '1'; rank <= '8'; rank++) {
			if(rankBetween(rank, startSquare.getRank(), endSquare.getRank()))
				squaresBetween.add(new Square(startSquare.getFile(), rank));
		}
		
		for(Square square : squaresBetween) {
			if(hasPiece(square))
				clearPathExists = false;
		}
		
		return clearPathExists;
	}

	/**
	 *	Checks if there is a diagonal file path between two Squares.
	 *	
	 *	@param	startSquare 	the first Square
	 *	@param	endSquare		the second Square
	 *	@return					true if clear, false if not
	 */
	private boolean clearPathInDiagonal(Square startSquare, Square endSquare) {
		boolean clearPathExists = true;
		
		int rankAdjustment = (startSquare.getRank() < endSquare.getRank() ? 1 : -1);
		int fileAdjustment = (startSquare.getFile() < endSquare.getFile() ? 1 : -1);
		char rank = (char)(startSquare.getRank() + rankAdjustment);
		char file = (char)(startSquare.getFile() + fileAdjustment);
		
		while(rank != endSquare.getRank()) {
			if(hasPiece(new Square(file, rank)))
				clearPathExists = false;
			
			rank = (char) (rank + rankAdjustment);
			file = (char) (file + fileAdjustment);
		}
		
		return clearPathExists;
	}
	
	/**
	 *	Checks if this file is between a range.
	 *	
	 *	@param	file		the file to check
	 *	@param	startFile	the starting file range
	 **	@param	endFile		the ending file range
	 *	@return				true if yes, false if not
	 */
	private boolean rankBetween(char rank, char startRank, char endRank) {
		if (rank > startRank && rank < endRank)
			return true;
		else if (rank > endRank && rank < startRank)
			return true;
		else
			return false;
	}
	
	/**
	 *	Checks if this file is between a range.
	 *	
	 *	@param	file		the file to check
	 *	@param	startFile	the starting file range
	 **	@param	endFile		the ending file range
	 *	@return				true if yes, false if not
	 */
	private boolean fileBetween(char file, char startFile, char endFile) {
		if (file > startFile && file < endFile)
			return true;
		else if (file > endFile && file < startFile)
			return true;
		else
			return false;
	}
}